package org.oimwrapper.connectors.common;

import Thor.API.*;
import Thor.API.Operations.*;
import com.thortech.xl.dataobj.*;
import java.util.*;

import org.oimwrapper.connectors.*;

/**
 * Scans for recon events with a no match found status and links them to a user.<p>
 *
 * This task expects the following parameters, in addition to the standard
 * parameters in the {@link OIMTaskBase} class.
 * <ul>
 * <li><code>ResourceName</code> The name of the resource object to search for events from.</li>
 * <li><code>EventStatus</code> The status of the events to search for.  This defaults to 'no match found'.</li>
 * <li><code>CloseUnmatchedEvents</code> Indicates that events not matched by this task should be closed, which will
 *                                       prevent the event from being processed again.  This defaults to true.</li>
 * <li><code>StartDate</code> The start date to begin looking for recon events on, in yyyy-MM-dd format.  This
 *                            defaults to the beginning of time.</li>
 * <li><code>Threads</code> The number of concurrent threads to run to process events.  Setting this to 1 disables
 *                          threading.</li>
 * </ul>
 */
public abstract class OIMAltReconMatchBase extends OIMTaskBase {

	/**
	 * True if a stop has been requested for this task.
	 */
	private boolean stop;
	
	/**
	 * The reconcilation operations interface to use.
	 */
	protected tcReconciliationOperationsIntf reconOp;
	
	/**
	 * <code>true</code> if unmatched events should be closed, or <code>false</code> if they should be 
	 * left open.
	 */
	protected boolean closeUnmatched;
    
	/**
	 * The date to search for recon events after.
	 */
	protected String startDate;
	
	/**
	 * The date to search for recon events before.
	 */
	protected String endDate;
	
	/**
	 * Default constructor.
	 */
	public OIMAltReconMatchBase() {
		super("OIMWRAPPER.TASK");
		stop = false;
	}
	
	/**
	 * Constructor to specify the log category.
	 * 
	 * @param LogCategory The log category to use.
	 */
	public OIMAltReconMatchBase(String LogCategory) {
		super(LogCategory);
		stop = false;
	}

	/**
	 * This method is called by the scheduler to execute the task instance.
	 */
	public final void execute() {
		logger.debug("Entering altReconMatchBase.execute()");
		try {
			checkOimVersion("9.1");
			reconOp = (tcReconciliationOperationsIntf) getUtility(tcReconciliationOperationsIntf.class);
			Map searchFor = new HashMap();
			long objectKey = getResourceObject(getDefaultAttribute("ResourceName", ""));
			if (objectKey != 0L) {
				searchFor.put("Objects.Key", Long.toString(objectKey));
			}
			searchFor.put("Reconciliation Manager.Status", getDefaultAttribute("EventStatus", tcRCE.STATUS_NO_MATCH));
			closeUnmatched = getBooleanAttribute("CloseUnmatchedEvents", true);
			startDate = getDefaultAttribute("StartDate", formatDate((new java.util.Date(0)).getTime(), "yyyy-MM-dd"));
			endDate = formatDate((new java.util.Date()).getTime(), "yyyy-MM-dd");
			int maxWorkers = Integer.parseInt(getDefaultAttribute("Threads", "10"));
			tcResultSet results = reconOp.findReconciliationEvent(searchFor, startDate, endDate);
			Map sortedByUser = new HashMap();
			for (int i = 0; !stop && i < results.getRowCount(); i++) {
				results.goToRow(i);
				logger.debug("Sorting event " + Integer.toString(i + 1) + " of " + Integer.toString(results.getRowCount()));
				long eventKey = results.getLongValue("Reconciliation Manager.Key");
				Long userKey = new Long(matchToUser(getEventData(eventKey)));
				Set eventsForUser = (Set) sortedByUser.get(userKey);
				if (eventsForUser == null) {
					eventsForUser = new HashSet();
					sortedByUser.put(userKey, eventsForUser);
				}
				eventsForUser.add(new Long(eventKey));
			}
			int i = 0;
			for (Iterator iterator = sortedByUser.keySet().iterator(); !stop && iterator.hasNext(); i++) {
				Long userKey = (Long) iterator.next();
				Set events = (Set) sortedByUser.get(userKey);
				logger.debug("Processing user " + Integer.toString(i + 1) + " of " + Integer.toString(results.getRowCount()));
				startThread(maxWorkers, new worker(this, userKey.longValue(), events));
			}
			waitForAllThreads();
		} catch (Exception e) {
			OIMExceptionHandler.handleException(logger, e, getClass());
		}
		logger.debug("Exiting altReconMatchBase.execute()");
	}
	
	/**
	 * Gets the single-valued event data for a recon event.
	 * 
	 * @param EventKey The key of the recon event to get the data for.
	 * @return A Map containing the data for that event.
	 * @exception Exception
	 */
	public final Map getEventData(long EventKey) throws Exception {
		logger.debug("Entering altReconMatchBase.getEventData()");
		Map result = new HashMap();
		Map[] results = getOimData("select orf_fieldname, rcd_value from rcd inner join orf on rcd.orf_key = orf.orf_key where rce_key=" + Long.toString(EventKey));
		for (int i = 0; i < results.length; i++) {
			String name = (String) results[i].get("Objects.Reconciliation Fields.Name");
			String value = (String) results[i].get("Reconciliation Manager.Event Data.Value");
			result.put(name, value);
		}
		logger.debug("Exiting altReconMatchBase.getEventData()");
		return result;
	}
	
	/**
	 * Processes a single recon event.
	 * 
	 * @param UserKey The key of the user that the unmatched recon events link to.
	 * @param Events A Set of event keys to process for the user.
	 */
	public void processSingleUser(long UserKey, Set Events) {
		logger.debug("Entering altReconMatchBase.processSingleUser()");
		try {
			for (Iterator iterator = Events.iterator(); !stop && iterator.hasNext(); ) {
				Long eventKey = (Long) iterator.next();
				if (UserKey != 0L) {
					logger.debug("Attempting to apply existing matching rules for " + eventKey.toString());
					reconOp.processReconciliationEvent(eventKey.longValue());
					Map searchFor = new HashMap();
					searchFor.put("Reconciliation Manager.Key", eventKey.toString());
					tcResultSet status = reconOp.findReconciliationEvent(searchFor, startDate, endDate);
					status.goToRow(0);
					String eventStatus = status.getStringValue("Reconciliation Manager.Status");
					logger.debug("eventStatus = " + eventStatus);
					if (eventStatus.equalsIgnoreCase(tcRCE.STATUS_NO_MATCH)) {
						reconOp.linkEventToUser(eventKey.longValue(), UserKey);
					}
				} else if (closeUnmatched) {
					reconOp.closeReconciliationEvent(eventKey.longValue());
				}
			}
		} catch (Exception e) {
			OIMExceptionHandler.handleException(logger, e, getClass());
		}
		logger.debug("Exiting altReconMatchBase.processSingleUser()");
	}
	
	/**
	 * Matches a recon data event to a user.   This should return zero if no user is matched.
	 * This function is called serially, so it should be designed to execute very quickly.
	 * 
	 * @param Data A Map containing the recon event data for 
	 * @return The user key of the matched user, or a zero if a matching user is not found.
	 * @exception Exception
	 */
	public abstract long matchToUser(Map Data) throws Exception;
	
	/**
	 * This method is called by the scheduler to stop a running task instance.
	 * 
	 * @return <code>true</code>
	 */
	public final boolean stop() {
		logger.debug("Entering altReconMatchBase.stop()");
		logger.warn(" ---- Stopping current task ---- ");
		stop = true;
		logger.debug("Exiting altReconMatchBase.stop()");
		return stop;
	}
	
	/**
	 * A worker thread to process one recon event.
	 */
	private class worker extends OIMTaskThreadBase {
		
		/**
		 * The calling task.
		 */
		private OIMAltReconMatchBase task;
		
		/**
		 * The key of the user that the unmatched recon events link to.
		 */
		private long userKey;
		
		/**
		 * A Set of event keys to process for the user.
		 */
		private Set events;
		
		/**
		 * Default constructor
		 * 
		 * @param Task The calling task.
		 * @param UserKey The key of the user that the unmatched recon events link to.
		 * @param Events A Set of event keys to process for the user.
		 */
		private worker(OIMAltReconMatchBase Task, long UserKey, Set Events) {
			super(Task);
			task = Task;
			userKey = UserKey;
			events = Events;
		}
		
		/**
		 * Processes a single event.
		 */
		public void runOimThread() {
			task.processSingleUser(userKey, events);
		}
	}
}